﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "Platform.h"

#ifdef PLATFORM_WINDOWS

namespace UX_STL
{
namespace _platform_
{

//------------------------- Platform ------------------------------//
/*
=================================================
	смена разрешения окна
=================================================
*/
	bool WinApiPlatform::ChangeScreenResolution (const uvec2 &vSize, const uint8 uColors, uint uFreq)
	{
		DEVMODEA	mode = {0};
		mode.dmSize				= sizeof( mode );
		mode.dmPelsWidth		= (DWORD) vSize.x;
		mode.dmPelsHeight		= (DWORD) vSize.y;
		mode.dmBitsPerPel		= (DWORD) uColors;
		mode.dmDisplayFrequency	= (DWORD) uFreq;
		mode.dmFields			= DM_BITSPERPEL | DM_PELSWIDTH |
								  DM_PELSHEIGHT | (uFreq ? DM_DISPLAYFREQUENCY : 0);

		if ( ChangeDisplaySettings( &mode, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL )
		{
			mode.dmFields &= ~DM_DISPLAYFREQUENCY;

			CHECK_ERR( ChangeDisplaySettings( &mode, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL );
			LOG( (string() << "Can't set display settings with frequency " << uFreq << '.').cstr(), e_log_msg::WARNING_3 );
		}

		return true;
	}

/*
=================================================
	заполняет список доступными режимами монитора
=================================================
*/
	bool WinApiPlatform::GetScreenModes (array<TScreenMode> &aModes)
	{
		DEVMODEA	temp;
		uint				i = 0;
		array<TScreenMode>	a_modes;

		while ( EnumDisplaySettings( null, i, &temp ) == TRUE )
		{
			if ( temp.dmPelsWidth >= 640 and temp.dmPelsHeight >= 480 and
				 temp.dmBitsPerPel >= 16 and temp.dmDisplayFrequency >= 60 )
			{
				// compare whis older
				bool			b_flag = false;
				TScreenMode		s_mode( uvec2( temp.dmPelsWidth, temp.dmPelsHeight ),
										temp.dmBitsPerPel, temp.dmDisplayFrequency );

				FOR( j, a_modes ) {
					if ( a_modes[j] == s_mode ) {
						b_flag = true;
						break;
					}
				}

				if ( !b_flag )
					a_modes.PushBack( s_mode );
			}
			++i;
		}

		aModes.Clear();
		aModes.Reserve( a_modes.Count() );

		FOR( j, a_modes )
		{
			DEVMODEA	mode = {0};
			mode.dmSize				= sizeof( mode );
			mode.dmPelsWidth		= (DWORD) a_modes[j].vResolution.x;
			mode.dmPelsHeight		= (DWORD) a_modes[j].vResolution.y;
			mode.dmBitsPerPel		= (DWORD) a_modes[j].uColors;
			mode.dmDisplayFrequency	= (DWORD) a_modes[j].uFrequency;
			mode.dmFields			= DM_BITSPERPEL | DM_PELSWIDTH |
									  DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;

			if ( ChangeDisplaySettings( &mode, CDS_TEST | CDS_FULLSCREEN ) == DISP_CHANGE_SUCCESSFUL )
				aModes.PushBack( a_modes[j] );
		}

		return true;
	}

/*
=================================================
	записывает текущее время и дату в строку
=================================================
*/
	bool WinApiPlatform::GetDate (string &str, e_date::type eType)
	{
		SYSTEMTIME	time;
		GetLocalTime( &time );

		if ( EnumCmp( eType, e_date::TIME ) )
		{
			if ( time.wHour < 10 )		str << '0';
			str << time.wHour << ':';
			if ( time.wMinute < 10 )	str << '0';
			str << time.wMinute << ':';
			if ( time.wSecond < 10 )	str << '0';
			str << time.wSecond;
		}
		if ( EnumCmp( eType, e_date::MSEC ) )
		{
			str << '.';
			if ( time.wMilliseconds < 10 )	str << "00";	else
			if ( time.wMilliseconds < 100 )	str << '0';
			str << time.wMilliseconds;
		}
		if ( EnumCmp( eType, e_date::DATE ) )
		{
			if ( EnumCmp( eType, e_date::TIME_MSEC ) )  str << ' ';
			str << time.wDay << '/' << time.wMonth << '/' << time.wYear;
		}
		return true;
	}

/*
=================================================
	записывает в строку информацию о системе
=================================================
*/
	void WinApiPlatform::GetSystemInfoString (const TSystemInfo &sInfo, string &sStr)
	{
		sStr << "---------------\nSystem info\n---------------\n"
			 << "OS:        " << sInfo.sOSName << "\n";

		FOR( i, sInfo.aCPUs )
		{
			sStr << "CPU_" << i << ":     "
				 << sInfo.aCPUs[i].sName << " ("
				 << sInfo.aCPUs[i].uFreq << " Mhz"
				 << " x" << sInfo.aCPUs[i].uNumCores << ")\n";
		}

		FOR( i, sInfo.aGPUs )
		{
			sStr << "GPU_" << i << ":     " << sInfo.aGPUs[i].sName << ' ' << (uint)sInfo.aGPUs[i].uVRAM << " Mb\n";
		}
		
		sStr << "RAM:       " << string().FormatI( sInfo.uRAM, 3, ' ', 10 ) << " Mb\n"
			 << "VirtMem:   " << string().FormatI( sInfo.uVirtualMemory, 3, ' ', 10 ) << " Mb\n";
	}
	
/*
=================================================
	возвращает путь к exe/dll файлу
=================================================
*/
	bool WinApiPlatform::GetModulePath (string &sPath, uni_c_string pModuleName)
	{
		return GetModulePath( sPath, (void *)GetModuleHandle(pModuleName.cstr()) );
	}

	bool WinApiPlatform::GetModulePath (string &sPath, void *pModule)
	{
		char		a_buf[MAX_PATH<<2] = {0};
		const usize	u_len = GetModuleFileName( (HMODULE)pModule, a_buf, (DWORD)CountOf(a_buf) );

		if ( u_len != 0 )
		{
			sPath = FileAddressUtils::GetPath( uni_c_string( a_buf ) );
			return true;
		}
		return false;
	}

/*
=================================================
	возвращает путь к системной директории
=================================================
*/
	bool WinApiPlatform::GetSystemPath (string &sPath)
	{
		char		a_buf[MAX_PATH<<2];
		const usize	u_len = GetSystemDirectory( a_buf, (DWORD)CountOf(a_buf) );

		if ( u_len != 0 )
		{
			sPath = uni_c_string( a_buf, u_len );
			return true;
		}
		return false;
	}

}	// _platform_
}	// UX_STL

/*
=================================================
	собирает информацию о системе
=================================================
*/
#	undef _WINDOWS_
#	undef WINAPI
#	undef DECLARE_HANDLE
#	define WIN32_LEAN_AND_MEAN 1

#	include <WbemCli.h>
#	include <comutil.h>

#	pragma comment(lib, "comsuppw.lib")
#	pragma comment(lib, "wbemuuid.lib")

namespace UX_STL
{
namespace _platform_
{

	bool WinApiPlatform::GetSystemInfo (TSystemInfo &sInfo)
	{
		TRY{

		::OSVERSIONINFOEXA	osvi;
		
		ZeroMem( osvi );
		osvi.dwOSVersionInfoSize = sizeof(osvi);

		// OS
		if ( ::GetVersionExA( (::OSVERSIONINFOA *) &osvi ) == 0 ) 
			sInfo.sOSName = "Unknown"; 
		else
		{
			sInfo.sOSName = "Microsoft Windows";

			if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_NT and osvi.dwMajorVersion > 4 )
			{
				if ( osvi.dwMajorVersion == 6 )
				{
					if ( osvi.dwMinorVersion == 0 )
					{
						if ( osvi.wProductType == VER_NT_WORKSTATION )
							sInfo.sOSName += " Vista";
						else
							sInfo.sOSName += " Server 2008";
					}
					else
					if ( osvi.dwMinorVersion == 1 )
					{
						if ( osvi.wProductType == VER_NT_WORKSTATION )
							sInfo.sOSName += " 7";
						else
							sInfo.sOSName += " Server 2008 R2";
					}
				}
				else
				if ( osvi.dwMajorVersion == 5 )
				{
					if ( osvi.dwMinorVersion == 0 )
					{
						sInfo.sOSName += " 2000";
					}
					else
					if ( osvi.dwMinorVersion == 1 )
					{
						sInfo.sOSName += " XP";
					}
					else
					if ( osvi.dwMinorVersion == 2 )
					{
						if ( GetSystemMetrics( SM_SERVERR2 ) )
							sInfo.sOSName += " Server 2003 R2";
						else
						if ( osvi.wSuiteMask & VER_SUITE_STORAGE_SERVER )
							sInfo.sOSName += " Storage Server 2003";
						else
						if ( osvi.wSuiteMask & VER_SUITE_WH_SERVER )
							sInfo.sOSName += " Home Server";
						else
						if ( osvi.wProductType == VER_NT_WORKSTATION )
							sInfo.sOSName += " XP Professional x64 Edition";
						else
							sInfo.sOSName += " Server 2003";
					}
				}
			}
			
			{
				typedef BOOL (CALLBACK *PISWOW64)(HANDLE, PBOOL);

				BOOL		b_is_wow64			= FALSE;
				PISWOW64	p_is_wow64_process	= (PISWOW64)::GetProcAddress( ::GetModuleHandleA("kernel32"), "IsWow64Process" );

				if ( p_is_wow64_process != null )
					p_is_wow64_process( GetCurrentProcess(), &b_is_wow64 );

				sInfo.bIs64bit = (b_is_wow64 == TRUE);

				sInfo.sOSName << (sInfo.bIs64bit ? " 64-bit" : " 32-bit");
			}

			if ( osvi.wServicePackMajor != 0  and osvi.wServicePackMinor != 0 )
			{
				sInfo.sOSName << " SP " << osvi.wServicePackMajor;

				if ( osvi.wServicePackMinor != 0 )
					sInfo.sOSName << '.' << osvi.wServicePackMinor;
			}
				

			sInfo.sOSName << " (build " << (uint)osvi.dwMajorVersion << '.' << (uint)osvi.dwMinorVersion << '.'
						  << (uint)osvi.dwBuildNumber << ")";
		} 

		// CPU
		::HKEY	h_key;
		::DWORD	dw_type	= 0;
		::DWORD	dw_size	= 0;
		::DWORD	mhz;
		
		::SYSTEM_INFO	s_sys_info; 
		ZeroMem( s_sys_info );
		::GetSystemInfo( &s_sys_info );

		uint	cpu_count = s_sys_info.dwNumberOfProcessors;
		bool	ret;
		
		for (uint i = 0; i < cpu_count; ++i)
		{
			string	cpu_reg( "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\" );

			ret  = ::RegOpenKeyExA( (::HKEY)HKEY_LOCAL_MACHINE, (cpu_reg << i).cstr(),
									0, KEY_READ, &h_key ) == ERROR_SUCCESS;

			ret	&= RegQueryValueExA( h_key, "ProcessorNameString", null, &dw_type, null, &dw_size ) == ERROR_SUCCESS;
			
			if ( !ret || dw_size <= 0 ) {
				WARNING( "" );
				continue;
			}

			string	s_processor_name;
			s_processor_name.Reserve( dw_size+1 );

			ret &= RegQueryValueExA( h_key, "ProcessorNameString", null, &dw_type, (LPBYTE)s_processor_name.ptr(), &dw_size) == ERROR_SUCCESS;

			s_processor_name.CalculateLength();

			dw_size = sizeof(DWORD);
			ret &= RegQueryValueExA( h_key, "~MHz", null, &dw_type, (LPBYTE)&mhz, &dw_size ) == ERROR_SUCCESS;
			ASSERT( ret );

			::RegCloseKey( h_key );
			
			
			if ( !ret ) {
				WARNING( "" );
				continue;
			}

			bool	b_found = false;

			FOR( j,sInfo.aCPUs )
			{
				if ( sInfo.aCPUs[j].sName == s_processor_name )
				{
					sInfo.aCPUs[j].uNumCores++;
					b_found = true;
					break;
				}
			}

			if ( b_found )
				continue;

			TSystemInfo::TProcessor		s_cpu;

			s_cpu.sName		= s_processor_name;
			s_cpu.uFreq		= mhz;
			s_cpu.uNumCores	= 1;

			sInfo.aCPUs.PushBack( s_cpu );
		}


		// GPU
		string	s_last_device_id;	

		for (uint i = 0;; ++i)
		{
			TSystemInfo::TVideoCard		s_gpu;
			::DISPLAY_DEVICEA			dd;

			ZeroMem( dd );
			dd.cb = sizeof(dd);

			if ( EnumDisplayDevicesA( null, i, &dd, 0 ) == FALSE )
				break;
			else
			{
				if ( dd.DeviceID == s_last_device_id || dd.DeviceID[0] == 0 )
					continue;

				s_last_device_id = dd.DeviceID;

				s_gpu.uVRAM = 0;
				s_gpu.sName	= dd.DeviceString;
			}


			IWbemLocator *	locator = null;
			HRESULT			hr		= E_FAIL,
							chr		= CoInitialize( null );

			if ( SUCCEEDED( chr ) )
				hr = CoCreateInstance( CLSID_WbemLocator, null, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (void**)&locator );

			if ( SUCCEEDED( hr ) )
			{
				IWbemServices* services = null;
				hr = locator->ConnectServer( L"root\\cimv2", null, null, null, 0, null, null, &services );
				locator->Release();

				if ( SUCCEEDED( hr ) )
				{
					hr = CoSetProxyBlanket( services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, null, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, null, EOAC_NONE );
					if ( FAILED( hr ) )
						services->Release();
					else
					{
						IEnumWbemClassObject *	instance_enum = null;
						hr = services->CreateInstanceEnum( L"Win32_VideoController", WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, null, &instance_enum );
						services->Release();
					
						if ( SUCCEEDED( hr ) )
						{
							IWbemClassObject *	instance;
							::ULONG				objectsReturned = 0;

							while( true )
							{
								hr = instance_enum->Next( WBEM_INFINITE, 1, &instance, &objectsReturned );
								if ( FAILED( hr ) )
									break;

								if ( objectsReturned != 1 )
									break;

								VARIANT v;
								VariantInit(&v);
								hr = instance->Get( L"AdapterRAM", 0, &v, null, null );
							
								if ( SUCCEEDED( hr ) )
									s_gpu.uVRAM = uint( V_UI4(&v) >> 20 );

								VariantClear(&v);
								instance->Release();
								instance = null;
							}
							instance_enum->Release();
						}
					}
				}
			}

			if ( SUCCEEDED( chr ) )
				CoUninitialize();



			// GPU Model
			usize	u_pos = 0;
			if ( s_gpu.sName.Find( "AMD",	 u_pos ) )	s_gpu.eGPUModel = e_gpu_model::AMD;		else
			if ( s_gpu.sName.Find( "ATI",	 u_pos ) )	s_gpu.eGPUModel = e_gpu_model::ATI;		else
			if ( s_gpu.sName.Find( "NVIDIA", u_pos ) )	s_gpu.eGPUModel = e_gpu_model::NVIDIA;	else
			if ( s_gpu.sName.Find( "INTEL",	 u_pos ) )	s_gpu.eGPUModel = e_gpu_model::INTEL;	else
														s_gpu.eGPUModel = e_gpu_model::UNKNOWN;

			sInfo.aGPUs.PushBack( s_gpu );
		}

		
		// RAM
		MEMORYSTATUSEX stat;
		stat.dwLength = sizeof(stat);
		GlobalMemoryStatusEx( &stat );

		sInfo.uRAM = uint( stat.ullTotalPhys >> 20 );
		sInfo.uVirtualMemory = uint( stat.ullTotalVirtual >> 20 );
		return true;

		}
		CATCH( ..., { return false; } );
	}

}	// _platform_
	

	

namespace UXTypesExt
{
	
//------------------------ TAPIErrorChecker -----------------------//
/*
=================================================
	очистить стэк ошибок WinAPI
=================================================
*/
	void TAPIErrorChecker::ClearOSErrors ()
	{
		for (uint i = 0; i < _GetMaxErrors() and ::GetLastError() != 0; ++i)  {}
	}

/*
=================================================
	проверить не было ли ошибки
=================================================
*/
	bool TAPIErrorChecker::CheckOSError (uni_c_string pOSFunc, uni_c_string pFunc, uni_c_string pFile, uint uLine)
	{
		LPSTR	lpMsgBuf = null;
		DWORD	dw		 = ::GetLastError();

		if ( dw == 0 )
			return true;

		DWORD	dw_count = ::FormatMessageA(
									FORMAT_MESSAGE_ALLOCATE_BUFFER |
									FORMAT_MESSAGE_FROM_SYSTEM |
									FORMAT_MESSAGE_IGNORE_INSERTS,
									null,
									dw,
									MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT ),
									(LPTSTR) &lpMsgBuf,
									0, null );

		string	str("WinAPI error: ");

		if ( dw_count > 5 )
			str.Append( uni_c_string( (const char *)lpMsgBuf, dw_count-2 ) );
		else
			str << "code: 0x" << string().FormatI( (uint)dw, 16 );

		str << ", in " << pOSFunc;
		str << ", function: " << pFunc;

		LOG_EXT( str.cstr(), e_log_msg::ERROR_NL_2, pFile.cstr(), uLine );

		::LocalFree( lpMsgBuf );
		return false;
	}
	
/*
=================================================
	проверить не было ли ошибок
=================================================
*/
	bool TAPIErrorChecker::CheckOSErrors (uni_c_string pFile, uint uLine)
	{
		LPSTR	lpMsgBuf = null;
		DWORD	dw;
		bool	ret = true;

		for (uint i = 0; i < _GetMaxErrors() and (dw = ::GetLastError()) != 0; ++i)
		{
			DWORD	dw_count = ::FormatMessageA(
										FORMAT_MESSAGE_ALLOCATE_BUFFER |
										FORMAT_MESSAGE_FROM_SYSTEM |
										FORMAT_MESSAGE_IGNORE_INSERTS,
										null,
										dw,
										MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT ),
										(LPTSTR) &lpMsgBuf,
										0, null );
			
			string	str("WinAPI error: ");

			if ( dw_count > 5 )
				str.Append( uni_c_string( (const char *)lpMsgBuf, dw_count-2 ) );
			else
				str << "code: 0x" << string().FormatI( (uint)dw, 16 );

			LOG_EXT( str.cstr(), e_log_msg::ERROR_NL_2, pFile.cstr(), uLine );

			::LocalFree( lpMsgBuf );
			ret = false;
		}

		return ret;
	}

//-------------------------------------------------------------------

}	// UXTypesExt
}	// UX_STL

#endif	// PLATFORM_WINDOWS